﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text;
using System.Web;
using System.Web.Routing;
using Nop.Core;
using Nop.Core.Domain.Directory;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Payments;
using Nop.Core.Domain.Shipping;
using Nop.Core.Plugins;
using Nop.Plugin.Payments.PayU_ZA.Controllers;
using Nop.Services.Configuration;
using Nop.Services.Directory;
using Nop.Services.Localization;
using Nop.Services.Orders;
using Nop.Services.Payments;
using Nop.Services.Tax;
using Nop.Web.Framework.Controllers;
using Nop.Core.Domain.Messages;
using Nop.Services.Messages;
using Nop.Core.Configuration;
using System.Xml;
using System.Linq;
using Nop.Services.Logging;

namespace Nop.Plugin.Payments.PayU_ZA {
    public class PayU_ZAPaymentProcessor : BasePlugin, IPaymentMethod {
        PayU_ZAPaymentSettings _payU_ZAStandardSettings;
        private readonly HttpContextBase _httpContext;
        private readonly IWebHelper _webHelper;
        private IQueuedEmailService _queuedEmailService;
        private ISettingService _settings;
        private ILogger _logger;
        private IOrderService _orderService;


        public PayU_ZAPaymentProcessor(PayU_ZAPaymentSettings payU_ZAStandardSettings, IWebHelper webhelper, HttpContextBase httpcontext, IQueuedEmailService queuedEmailService, ISettingService settings, ILogger Logger, IOrderService orderService) {
            _httpContext = httpcontext;
            _payU_ZAStandardSettings = payU_ZAStandardSettings;
            _webHelper = webhelper;
            _queuedEmailService = queuedEmailService;
            _settings = settings;
            _logger = Logger;
            _orderService = orderService;

        }

        private string GetPayUUrl() {
            return _payU_ZAStandardSettings.UseSandbox ? "https://staging.payu.co.za" :
                "https://www.payu.co.za";
        }


        /// <summary>
        /// Process a payment
        /// </summary>
        /// <param name="processPaymentRequest">Payment info required for an order processing</param>
        /// <returns>Process payment result</returns>
        public ProcessPaymentResult ProcessPayment(ProcessPaymentRequest processPaymentRequest) {
            var result = new ProcessPaymentResult();
            result.NewPaymentStatus = PaymentStatus.Pending;
            return result;
        }

        #region Processing realted

       
        static string serviceAPI = "/service/PayUAPI?wsdl";
        static string redirectAPI = "/rpp.do?PayUReference=";

        static string _soapEnvelope =
                     @"<SOAP-ENV:Envelope xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/' 
                                    xmlns:ns1='http://soap.api.controller.web.payjar.com/' 
                                    xmlns:ns2='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'>
                                    <SOAP-ENV:Header>
                                        <wsse:Security SOAP-ENV:mustUnderstand='1' xmlns:wsse='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'>
                                            <wsse:UsernameToken wsu:Id='UsernameToken-9' xmlns:wsu='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'>
                                                <wsse:Username></wsse:Username>
                                                <wsse:Password Type='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText'></wsse:Password>
                                            </wsse:UsernameToken>
                                        </wsse:Security>
                                    </SOAP-ENV:Header>
                                     <SOAP-ENV:Body>
                                     </SOAP-ENV:Body>
                </SOAP-ENV:Envelope>";

        /// <summary>
        /// Creates the HttpWebRequest object
        /// </summary>
        /// <returns>webRequest</returns>        
        private static HttpWebRequest CreateWebRequest(string url) {
            HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(url);
            webRequest.Headers.Add(@"SOAP:Action");
            webRequest.ContentType = "text/xml;charset=\"utf-8\"";
            webRequest.Accept = "text/xml";
            webRequest.Method = "POST";
            return webRequest;
        }


        /// <summary>
        /// Creates the SOAP envelope object
        /// </summary>
        /// <returns>soapEnvelopeXml</returns> 
        private static XmlDocument CreateSoapEnvelope(string content) {
            StringBuilder sb = new StringBuilder(_soapEnvelope);
            sb.Insert(sb.ToString().IndexOf("</SOAP-ENV:Body>"), content);

            // create an empty soap envelope
            XmlDocument soapEnvelopeXml = new XmlDocument();
            soapEnvelopeXml.LoadXml(sb.ToString());

            return soapEnvelopeXml;
        }

        /// <summary>
        /// Insert soap envelope into web request
        /// </summary>
        /// <returns></returns>
        private static void InsertSoapEnvelopeIntoWebRequest(XmlDocument soapEnvelopeXml, HttpWebRequest webRequest) {
            using (Stream stream = webRequest.GetRequestStream()) {
                soapEnvelopeXml.Save(stream);
            }
        }



        /// <summary>
        /// Post process payment (used by payment gateways that require redirecting to a third-party URL)
        /// </summary>
        /// <param name="postProcessPaymentRequest">Payment info required for an order processing</param>
        public void PostProcessPayment(PostProcessPaymentRequest postProcessPaymentRequest) {
            var soapContent = new StringBuilder();
            soapContent.Append("<ns1:setTransaction>");
            soapContent.AppendLine("<Api>ONE_ZERO</Api>");
            soapContent.AppendLine("<Safekey>" + _payU_ZAStandardSettings.SafeKey + "</Safekey>");

            //Payment type   
            soapContent.AppendLine("<TransactionType>PAYMENT</TransactionType>");
            soapContent.AppendLine("<Stage>'" + _payU_ZAStandardSettings.UseSandbox.ToString() + "'</Stage>");

            // Additional
            soapContent.AppendLine("<AdditionalInformation>");

            // The cancel url for redirect should be configured accordingly
            soapContent.AppendLine("<cancelUrl>" + _webHelper.GetStoreLocation(false) + "Plugins/PaymentPayU_ZA/CancelOrder</cancelUrl>");

            soapContent.AppendLine("<merchantReference>" + postProcessPaymentRequest.Order.Id + "</merchantReference>");

            // The return url for redirect should be configured accordingly
            soapContent.AppendLine("<returnUrl>" + _webHelper.GetStoreLocation(false) + "Plugins/PaymentPayU_ZA/PDTHandler?Order=" + postProcessPaymentRequest.Order.Id + "</returnUrl>");
            soapContent.AppendLine("<supportedPaymentMethods>" + _payU_ZAStandardSettings.PaymentMethods.ToString() + "</supportedPaymentMethods>");
            soapContent.AppendLine("</AdditionalInformation>");

            //Customer
            soapContent.AppendLine("<Customer>");
            soapContent.AppendLine("<merchantUserId>" + postProcessPaymentRequest.Order.Customer.Username + "</merchantUserId>");
            soapContent.AppendLine("<email>" + postProcessPaymentRequest.Order.Customer.BillingAddress.Email + "</email>");
            soapContent.AppendLine("<firstName>" + postProcessPaymentRequest.Order.Customer.BillingAddress.FirstName + "</firstName>");
            soapContent.AppendLine("<lastName>" + postProcessPaymentRequest.Order.Customer.BillingAddress.LastName + "</lastName>");
            soapContent.AppendLine("<mobile>" + postProcessPaymentRequest.Order.Customer.BillingAddress.PhoneNumber + "</mobile>");
            soapContent.AppendLine("</Customer>");

            // Basket
            soapContent.AppendLine("<Basket>");
            soapContent.AppendLine("<amountInCents>" + (Math.Round(postProcessPaymentRequest.Order.OrderTotal, 2) * 100).ToString("0", CultureInfo.InvariantCulture) + "</amountInCents>");
            soapContent.AppendLine("<currencyCode>ZAR</currencyCode>");
            soapContent.AppendLine("<description>PSPA Shopping basket: " + postProcessPaymentRequest.Order.Id + "</description>");
            soapContent.AppendLine("</Basket>");
            soapContent.AppendLine("</ns1:setTransaction>");
            // construct soap object

            XmlDocument soapEnvelopeXml = CreateSoapEnvelope(soapContent.ToString());

            // create username and password namespace
            XmlNamespaceManager nsmgr = new XmlNamespaceManager(soapEnvelopeXml.NameTable);
            nsmgr.AddNamespace("wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");


            // set soap username
            XmlNode userName = soapEnvelopeXml.SelectSingleNode("//wsse:Username", nsmgr);
            userName.InnerText = _payU_ZAStandardSettings.UserName;

            // set soap password
            XmlNode userPassword = soapEnvelopeXml.SelectSingleNode("//wsse:Password", nsmgr);
            userPassword.InnerText = _payU_ZAStandardSettings.Password;

            // construct web request object
            HttpWebRequest webRequest = CreateWebRequest(GetPayUUrl() + serviceAPI);

            // insert soap envelope into web request
            InsertSoapEnvelopeIntoWebRequest(soapEnvelopeXml, webRequest);

            // get the PayU reference number from the completed web request.
            string soapResult;
            ServicePointManager.Expect100Continue = true;
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;

            using (WebResponse webResponse = webRequest.GetResponse())
            using (StreamReader rd = new StreamReader(webResponse.GetResponseStream())) {
                soapResult = rd.ReadToEnd();
            }

            // create an empty soap result object
            XmlDocument soapResultXml = new XmlDocument();
            soapResultXml.LoadXml(soapResult.ToString());

            string success = soapResultXml.SelectSingleNode("//successful").InnerText;
            if (success == "true") {
                _logger.InsertLog(Core.Domain.Logging.LogLevel.Information, "PAYU_SETTRANSACTION", soapEnvelopeXml.ToString());
                StringBuilder builder = new StringBuilder();

                builder.Append(GetPayUUrl() + redirectAPI);

                // retrieve payU reference & build url with payU reference query string
                string payURef = soapResultXml.SelectSingleNode("//payUReference").InnerText;

                builder.Append(payURef);

                // Add the payment record to record to Gateway reference
                _orderService.AddPaymentItem(0, payURef, "", postProcessPaymentRequest.Order);
                // redirect to payU site
                _httpContext.Response.Redirect(builder.ToString());
            }
            else {


                var email = new QueuedEmail() {
                    EmailAccountId = 1,
                    FromName = _webHelper.GetStoreLocation() + " System issue",
                    ToName = _settings.GetSettingByKey<string>("commonsettings.FailureEmailTo"),
                    From = _settings.GetSettingByKey<string>("commonsettings.EmailFrom"),
                    To = _settings.GetSettingByKey<string>("commonsettings.FailureEmailTo"),
                    Subject = _webHelper.GetStoreLocation() + " error report",
                    Body = "The payment gateway has responded with an error. Please try again later" + Environment.NewLine + "Result: " + soapEnvelopeXml.ToString(),
                    CreatedOnUtc = DateTime.UtcNow
                };
                _queuedEmailService.InsertQueuedEmail(email);

                postProcessPaymentRequest.Order.PaymentStatus = PaymentStatus.Pending;
                var ErrorToShowCustomer = "The payment gateway has responded with an error. The service administrators have been altered. Please try again later";
                _httpContext.Response.Write(ErrorToShowCustomer);
            }
        }

        public ConfirmResult ConfirmPayment(string PaymentID) {
            var Result = new ConfirmResult();
            var soapContent = new StringBuilder();
            soapContent.Append("<ns1:getTransaction>");
            soapContent.AppendLine("<Api>ONE_ZERO</Api>");
            soapContent.AppendLine("<Safekey>" + _payU_ZAStandardSettings.SafeKey + "</Safekey>");
            soapContent.AppendLine("<AdditionalInformation><payUReference>" + PaymentID + "</payUReference></AdditionalInformation>");
            soapContent.AppendLine("</ns1:getTransaction>");

              XmlDocument soapEnvelopeXml = CreateSoapEnvelope(soapContent.ToString());

            // create username and password namespace
            XmlNamespaceManager nsmgr = new XmlNamespaceManager(soapEnvelopeXml.NameTable);
            nsmgr.AddNamespace("wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");


            // set soap username
            XmlNode userName = soapEnvelopeXml.SelectSingleNode("//wsse:Username", nsmgr);
            userName.InnerText = _payU_ZAStandardSettings.UserName;

            // set soap password
            XmlNode userPassword = soapEnvelopeXml.SelectSingleNode("//wsse:Password", nsmgr);
            userPassword.InnerText = _payU_ZAStandardSettings.Password;

            // construct web request object
            HttpWebRequest webRequest = CreateWebRequest(GetPayUUrl() + serviceAPI);

            // insert soap envelope into web request
            InsertSoapEnvelopeIntoWebRequest(soapEnvelopeXml, webRequest);

            // get the PayU reference number from the completed web request.
            string soapResult;

            //Bugfix if SSL auth can not be done. 
            ServicePointManager.Expect100Continue = true;
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;

            using (WebResponse webResponse = webRequest.GetResponse())
            using (StreamReader rd = new StreamReader(webResponse.GetResponseStream())) {
                soapResult = rd.ReadToEnd();
            }
           

            // create an empty soap result object
            XmlDocument soapResultXml = new XmlDocument();
            soapResultXml.LoadXml(soapResult.ToString());

            string success = soapResultXml.SelectSingleNode("//successful").InnerText;
            if (success == "true") {
                Result.Value = decimal.Parse(soapResultXml.SelectSingleNode("//amountInCents").InnerText, CultureInfo.InvariantCulture) / 100;
                Result.Ref = soapResultXml.SelectSingleNode("//merchantReference").InnerText;
                Result.UniqueRef = soapResultXml.SelectSingleNode("//payUReference").InnerText;
                Result.Message = soapResultXml.SelectSingleNode("//resultMessage").InnerText;
                Result.Transcript = webRequest.ToString() + Environment.NewLine + soapResult.ToString();
                Result.IsARefund = soapResultXml.SelectSingleNode("//transactionType").InnerText.Trim().ToUpper().Substring(0, 3) == "REF";
            }
            return Result;
        }

       

        #endregion

        public decimal GetAdditionalHandlingFee(IList<ShoppingCartItem> cart) {
            return new decimal(0);
        }

        public CapturePaymentResult Capture(CapturePaymentRequest capturePaymentRequest) {
            var result = new CapturePaymentResult();
            result.AddError("Capture method not supported");
            return result;
        }

        public RefundPaymentResult Refund(RefundPaymentRequest refundPaymentRequest) {
            var result = new RefundPaymentResult();
            result.AddError("Refund method not supported");
            return result;
        }

        public VoidPaymentResult Void(VoidPaymentRequest voidPaymentRequest) {
            var result = new VoidPaymentResult();
            result.AddError("Void method not supported");
            return result;
        }

        public ProcessPaymentResult ProcessRecurringPayment(ProcessPaymentRequest processPaymentRequest) {
            var result = new ProcessPaymentResult();
            result.AddError("Recurring payment not supported");
            return result;
        }

        public CancelRecurringPaymentResult CancelRecurringPayment(CancelRecurringPaymentRequest cancelPaymentRequest) {
            var result = new CancelRecurringPaymentResult();
            result.AddError("Recurring payment not supported");
            return result;
        }

        /// <summary>
        /// Gets a value indicating whether customers can complete a payment after order is placed but not completed (for redirection payment methods)
        /// </summary>
        /// <param name="order">Order</param>
        /// <returns>Result</returns>
        public bool CanRePostProcessPayment(Order order) {
            if (order == null)
                throw new ArgumentNullException("order");

            //let's ensure that at least 1 minute passed after order is placed
            if ((DateTime.UtcNow - order.CreatedOnUtc).TotalMinutes < 1)
                return false;

            return true;
        }

        /// <summary>
        /// Gets a route for provider configuration
        /// </summary>
        /// <param name="actionName">Action name</param>
        /// <param name="controllerName">Controller name</param>
        /// <param name="routeValues">Route values</param>
        public void GetConfigurationRoute(out string actionName, out string controllerName, out System.Web.Routing.RouteValueDictionary routeValues) {
            actionName = "Configure";
            controllerName = "PaymentPayU_ZA";
            routeValues = new RouteValueDictionary() { { "Namespaces", "Nop.Plugin.Payments.PayU_ZA.Controllers" }, { "area", null } };
        }

        /// <summary>
        /// Gets a route for payment info
        /// </summary>
        /// <param name="actionName">Action name</param>
        /// <param name="controllerName">Controller name</param>
        /// <param name="routeValues">Route values</param>
        public void GetPaymentInfoRoute(out string actionName, out string controllerName, out System.Web.Routing.RouteValueDictionary routeValues) {
            actionName = "PaymentInfo";
            controllerName = "PaymentPayU_ZA";
            routeValues = new RouteValueDictionary() { { "Namespaces", "Nop.Plugin.Payments.PayU_ZA.Controllers" }, { "area", null } };
        }

        public override void Install() {
            var settings = new PayU_ZAPaymentSettings() {
                IpnUrl = "PaymentPayU_ZA/IPNHandler",
                Password = "78cXrW1W",
                SafeKey = "{45D5C765-16D2-45A4-8C41-8D6F84042F8C}",
                UserName = "Staging Integration Store 1",
                UseSandbox = true,
                PaymentMethods = "CREDITCARD",
            };
            _settings.SaveSetting(settings);


            this.AddOrUpdatePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.RedirectionTip", "You will be redirected to the Pay U site to complete the order.");
            this.AddOrUpdatePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.UseSandbox", "Use Sandbox");
            this.AddOrUpdatePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.UseSandbox.Hint", "Check to enable Sandbox (testing environment).");
            this.AddOrUpdatePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.Safekey", "SafeKey Identity Token");
            this.AddOrUpdatePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.Safekey.Hint", "Specify SafeKey identity token");
            this.AddOrUpdatePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.EnableIpn", "Enable IPN (Instant Payment Notification)");
            this.AddOrUpdatePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.EnableIpn.Hint", "Check if IPN is enabled.");
            this.AddOrUpdatePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.EnableIpn.Hint2", "Leave blank to use the default IPN handler URL.");
            this.AddOrUpdatePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.IpnUrl", "IPN Handler");
            this.AddOrUpdatePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.IpnUrl.Hint", "Specify IPN Handler.");
            this.AddOrUpdatePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.UserName", "Gateway user name");
            this.AddOrUpdatePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.UserName.Hint", "To be used to authenticate with PayU when setting up transactions");
            this.AddOrUpdatePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.Password", "Gateway password");
            this.AddOrUpdatePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.Password.Hint", "To be used to authenticate with PayU when setting up transactions");
            this.AddOrUpdatePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.PaymentMethods", "Payment methods to request");
            this.AddOrUpdatePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.PaymentMethods.Hint", "A comma separated list of payment methods to request.");
            base.Install();
        }

        public override void Uninstall() {
            _settings.DeleteSetting<PayU_ZAPaymentSettings>();
            this.DeletePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.RedirectionTip");
            this.DeletePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.UseSandbox");
            this.DeletePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.UseSandbox.Hint");
            this.DeletePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.Safekey");
            this.DeletePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.Safekey.Hint");
            this.DeletePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.EnableIpn");
            this.DeletePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.EnableIpn.Hint");
            this.DeletePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.EnableIpn.Hint2");
            this.DeletePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.IpnUrl");
            this.DeletePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.IpnUrl.Hint");
            this.DeletePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.UserName");
            this.DeletePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.Password");
            this.DeletePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.UserName.Hint");
            this.DeletePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.Password.Hint");
            this.DeletePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.PaymentMethods");
            this.DeletePluginLocaleResource("Plugins.Payments.PayU_ZA.Fields.PaymentMethods.Hint");

            base.Uninstall();

        }

        public Type GetControllerType() {
            return typeof(PaymentPayU_ZAController);
        }

        public bool SupportCapture {
            get { return false; }
        }

        public bool SupportPartiallyRefund {
            get { return false; }
        }

        public bool SupportRefund {
            get { return false; }
        }

        public bool SupportVoid {
            get { return false; }
        }

        public RecurringPaymentType RecurringPaymentType {
            get { return Services.Payments.RecurringPaymentType.NotSupported; }
        }

        public PaymentMethodType PaymentMethodType {
            get { return Services.Payments.PaymentMethodType.Redirection; }
        }


        public void ReCheckPayment(Order o) {
            try {
                if (o != null && o.Payments.Where(f => f.Reference != "").Count() > 0) {
                    var res = this.ConfirmPayment(o.Payments.Where(f => f.Reference != "").First().Reference);
                    if (res.Value != 0) {
                        _orderService.ProcessAPayment(res.Value, res.Message, res.UniqueRef, o, res.Transcript, res.IsARefund);
                    }
                }
            }
            catch (Exception ex) {
                _logger.InsertLog(Core.Domain.Logging.LogLevel.Error, "Payment re-confirm issue", ex.Message, o.Customer);
            }
        }
    }
}
